using System;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections.Generic;

namespace Caramel
{
	public static class ExpressionExtensions
	{
		public static object Invoke<T>(this Expression<Action<T>> expression, T target)
		{
			var body = expression.Body;
			var call = body as MethodCallExpression;
			var parameters = new List<object>();

			foreach (var argument in call.Arguments)
			{
				var constant = argument as ConstantExpression;

				if (constant != null)
				{
					parameters.Add(constant.Value);
				}
				else
				{
					Expression<Func<object>> lambda = Expression.Lambda<Func<object>>(Expression.Convert(argument, typeof(object)));
					Func<object> func = lambda.Compile();
					parameters.Add(func());
				}
			}

			return call.Method.Invoke(target, parameters.ToArray());
		}

		//public static object Invoke<T, TReturn>(this Expression<Action<T>> expression, T target)
		//{
		//    return (TReturn)expression.Invoke(target);
		//}

		public static FieldInfo GetField(this LambdaExpression lambdaExpression)
		{
			return lambdaExpression.GetMember() as FieldInfo;
		}

		public static PropertyInfo GetProperty(this LambdaExpression lambdaExpression)
		{
			return lambdaExpression.GetMember() as PropertyInfo;
		}

		public static MethodInfo GetMethod(this LambdaExpression lambdaExpression)
		{
			return lambdaExpression.GetMember() as MethodInfo;
		}

		public static MemberInfo GetMember(this LambdaExpression lambdaExpression)
		{
			Expression expression = lambdaExpression;

			bool done = false;

			while (!done)
			{
				switch (expression.NodeType)
				{
					case ExpressionType.Lambda:
						expression = lambdaExpression.Body;
						break;

					case ExpressionType.Call:
						return ((MethodCallExpression)expression).Method;

					case ExpressionType.Convert:
						expression = ((UnaryExpression)expression).Operand;
						break;

					case ExpressionType.MemberAccess:
						return ((MemberExpression)expression).Member as PropertyInfo;

					default:
						done = true;
						break;
				}
			}

			return null;
		}
	}
}
